package com.atguigu.tingshu.order.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.account.AccountFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.delay.DelayMsgService;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.order.OrderDerate;
import com.atguigu.tingshu.model.order.OrderDetail;
import com.atguigu.tingshu.model.order.OrderInfo;
import com.atguigu.tingshu.order.helper.SignHelper;
import com.atguigu.tingshu.order.mapper.OrderDerateMapper;
import com.atguigu.tingshu.order.mapper.OrderDetailMapper;
import com.atguigu.tingshu.order.mapper.OrderInfoMapper;
import com.atguigu.tingshu.order.service.OrderInfoService;
import com.atguigu.tingshu.order.strategy.TradeStrategyFactory;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.account.AccountDeductVo;
import com.atguigu.tingshu.vo.order.OrderDetailVo;
import com.atguigu.tingshu.vo.order.OrderInfoVo;
import com.atguigu.tingshu.vo.order.TradeVo;
import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    private final RedisTemplate<Object, Object> redisTemplate;
    private final TradeStrategyFactory tradeStrategyFactory;
    private final AccountFeignClient accountFeignClient;
    private final UserFeignClient userFeignClient;
    private final OrderDetailMapper orderDetailMapper;
    private final OrderDerateMapper orderDerateMapper;
    private final DelayMsgService delayMsgService;
    private final RabbitService rabbitService;

    public OrderInfoServiceImpl(RedisTemplate<Object, Object> redisTemplate, TradeStrategyFactory tradeStrategyFactory, AccountFeignClient accountFeignClient, UserFeignClient userFeignClient, OrderDetailMapper orderDetailMapper, OrderDerateMapper orderDerateMapper, DelayMsgService delayMsgService, RabbitService rabbitService) {
        this.redisTemplate = redisTemplate;
        this.tradeStrategyFactory = tradeStrategyFactory;
        this.accountFeignClient = accountFeignClient;
        this.userFeignClient = userFeignClient;
        this.orderDetailMapper = orderDetailMapper;
        this.orderDerateMapper = orderDerateMapper;
        this.delayMsgService = delayMsgService;
        this.rabbitService = rabbitService;
    }


    @Override
    public OrderInfoVo trade(Long userId, TradeVo tradeVo) {
        // 获取并执行策略
        OrderInfoVo orderInfoVo = this.tradeStrategyFactory.getTradeStrategy(tradeVo.getItemType())
                .processTrade(userId, tradeVo);
        // 设置公共基础信息
        setBaseOrderInfo(userId, orderInfoVo, tradeVo);
        return orderInfoVo;
    }


    // 公共基础信息设置
    private void setBaseOrderInfo(Long userId, OrderInfoVo orderInfoVo, TradeVo tradeVo) {
        // 流水号（防止重复提交），生成唯一值存入缓存
        // 构建当前用户本次订单流水号Key
        String tradeNoKey = RedisConstant.ORDER_TRADE_NO_PREFIX + userId;
        // 生成本次订单流水号
        String tradeNo = IdUtil.fastSimpleUUID();
        // 将流水号存入Redis
        this.redisTemplate.opsForValue().set(tradeNoKey, tradeNo, RedisConstant.ORDER_TRADE_EXPIRE, TimeUnit.MINUTES);
        orderInfoVo.setTradeNo(tradeNo);
        // 支付方式，初始值为null，后续业务逻辑会赋值
        orderInfoVo.setPayWay(null);
        // 付款项目类型
        orderInfoVo.setItemType(tradeVo.getItemType());
        // 时间戳
        orderInfoVo.setTimestamp(DateUtil.current());
        // 签名(防止数据篡改)
        // 将订单VO转为Map，将VO中支付方式null值去掉
        Map<String, Object> paramsMap = BeanUtil.beanToMap(orderInfoVo, false, true);
        // 调用签名工具类对现有订单所有数据进行加密，获取签名
        String sign = SignHelper.getSign(paramsMap);
        orderInfoVo.setSign(sign);
    }


    @Override
    @GlobalTransactional(rollbackFor = {Exception.class})
    public Map<String, String> submitOrder(Long userId, OrderInfoVo orderInfoVo) {
        // 1 流水号校验，防止订单重复提交&防止页面停留超时
        // 构建订单流水号Key
        String tradeNoKey = RedisConstant.ORDER_TRADE_NO_PREFIX + userId;
        String tradeNo = orderInfoVo.getTradeNo();
        // 采用Lua脚本保证判断删除流水号原子性
        DefaultRedisScript<Long> redisScript = getRedisScript();
        long flag = redisTemplate.execute(redisScript, List.of(tradeNoKey), tradeNo);
        if (flag == 0) {
            throw new GuiguException(ResultCodeEnum.ORDER_SUBMIT_TIMEOUT);
        }
        if (flag == -1) {
            throw new GuiguException(ResultCodeEnum.ORDER_SUBMIT_REPEAT);
        }
        // 2 签名校验，防止订单数据篡改
        // 将提交订单VO参数转为Map，加签时支付方式为null，验签时需将去除支付方式 payWay
        Map<String, Object> paramsMap = BeanUtil.beanToMap(orderInfoVo, false, false);
        paramsMap.remove("payWay");
        SignHelper.checkSign(paramsMap);
        // 3 保存订单及订单明细、订单减免（order_info，order_detail，order_derate）
        OrderInfo orderInfo = this.saveOrderInfo(orderInfoVo, userId);
        // 4 处理支付方式：1103 余额支付的情况
        if (SystemConstant.ORDER_PAY_ACCOUNT.equals(orderInfoVo.getPayWay())) {
            // 4.1 远程调用账户服务检查及扣减账户余额
            AccountDeductVo accountDeductVo = new AccountDeductVo();
            accountDeductVo.setOrderNo(orderInfo.getOrderNo());
            accountDeductVo.setUserId(userId);
            accountDeductVo.setAmount(orderInfo.getOrderAmount());
            accountDeductVo.setContent(orderInfo.getOrderTitle());
            Result<Void> checkAndDeductResult = this.accountFeignClient.checkAndDeduct(accountDeductVo);
            if (Result.FAIL == checkAndDeductResult.getCode()) {
                throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
            }

            // 4.2 远程调用用户服务处理用户购买记录（虚拟物品发货）
            UserPaidRecordVo userPaidRecordVo = new UserPaidRecordVo();
            userPaidRecordVo.setOrderNo(orderInfo.getOrderNo());
            userPaidRecordVo.setUserId(userId);
            userPaidRecordVo.setItemType(orderInfo.getItemType());
            List<Long> itemIdList = orderInfoVo.getOrderDetailVoList().stream().map(OrderDetailVo::getItemId).toList();
            userPaidRecordVo.setItemIdList(itemIdList);
            Result<Void> savePaidRecordResult = this.userFeignClient.savePaidRecord(userPaidRecordVo);
            if (Result.FAIL == savePaidRecordResult.getCode()) {
                throw new GuiguException(ResultCodeEnum.USER_PAID_RECORD_SAVE_ERROR);
            }

            // 4.3 更新订单状态为已支付
            orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_PAID);
            this.baseMapper.updateById(orderInfo);
        }

        // 5 发送延迟消息。完成订单延迟关单
        // Redisson 实现延迟关单
//        this.delayMsgService.sendDelayMessage(
//                MqConst.QUEUE_CANCEL_ORDER,
//                orderInfo.getId().toString(),
//                MqConst.CANCEL_ORDER_DELAY_TIME
//        );
        // RabbitMQ 实现延迟关单
        this.rabbitService.sendDelayMessage(
                MqConst.EXCHANGE_CANCEL_ORDER,
                MqConst.ROUTING_CANCEL_ORDER,
                orderInfo.getId(),
                MqConst.CANCEL_ORDER_DELAY_TIME
        );
        // 6 返回订单号
        return Map.of("orderNo", orderInfo.getOrderNo());
    }

    @Override
    public OrderInfo saveOrderInfo(OrderInfoVo orderInfoVo, Long userId) {
        // 1 保存订单信息（order_info）
        // 拷贝订单VO参数
        OrderInfo orderInfo = BeanUtil.copyProperties(orderInfoVo, OrderInfo.class);
        orderInfo.setUserId(userId);
        String orderTitle = orderInfoVo.getOrderDetailVoList().stream()
                .map(OrderDetailVo::getItemName)
                .collect(Collectors.joining(", "));
        orderInfo.setOrderTitle(orderTitle);
        // 生成全局唯一订单编号 形式：当日日期+雪花算法
        String orderNo = DateUtil.today().replaceAll("-", "") + IdUtil.getSnowflakeNextId();
        orderInfo.setOrderNo(orderNo);

        orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_UNPAID);
        // 其余字段数据库中不存在，所以可以保存订单信息
        this.baseMapper.insert(orderInfo);

        orderInfo.setOrderStatusName(SystemConstant.ORDER_STATUS_NAME_UNPAID);
        orderInfo.setPayWayName(SystemConstant.ORDER_PAY_NAME_ACCOUNT);
        // 2 封装订单明细列表时，保存订单明细信息（order_detail）
        List<OrderDetail> orderDetailList = orderInfoVo.getOrderDetailVoList().stream()
                .map(orderDetailVo -> {
                    OrderDetail orderDetail = BeanUtil.copyProperties(orderDetailVo, OrderDetail.class);
                    orderDetail.setOrderId(orderInfo.getId());
                    this.orderDetailMapper.insert(orderDetail);
                    return orderDetail;
                })
                .toList();
        orderInfo.setOrderDetailList(orderDetailList);

        // 3 封装订单减免列表时，保存订单减免信息（order_derate）
        List<OrderDerate> orderDerateList = orderInfoVo.getOrderDerateVoList().stream()
                .map(orderDerateVo -> {
                    OrderDerate orderDerate = BeanUtil.copyProperties(orderDerateVo, OrderDerate.class);
                    orderDerate.setOrderId(orderInfo.getId());
                    this.orderDerateMapper.insert(orderDerate);
                    return orderDerate;
                })
                .toList();
        orderInfo.setOrderDerateList(orderDerateList);

        return orderInfo;
    }


    /**
     * 获取一个用于Redis的Lua脚本对象
     * 该方法定义了一个Lua脚本，用于在Redis中执行以下逻辑：
     * - 如果指定键的值等于给定的值，则删除该键，并返回1
     * - 如果指定键存在，但值不匹配，则返回-1
     * - 如果指定键不存在，则返回0
     *
     * @return DefaultRedisScript<Long> 初始化的Redis脚本对象，包含Lua脚本和结果类型定义
     */
    public DefaultRedisScript<Long> getRedisScript() {
        // 定义Lua脚本文本
        String scriptText = """
                if redis.call("get",KEYS[1]) == ARGV[1]
                then
                    redis.call("del",KEYS[1])
                    return 1
                else
                    if redis.call('exists', KEYS[1]) == 1
                    then
                           return -1
                       else
                       	return 0
                       end
                end""";
        // 返回初始化的Redis脚本对象,包含lua脚本文本和返回结果类型
        return new DefaultRedisScript<>(scriptText, Long.class);
    }

    @Override
    public OrderInfo getOrderInfoByOrderNo(Long userId, String orderNo) {
        // 1 根据订单编号查询订单信息
        OrderInfo orderInfo = this.baseMapper.selectOne(new LambdaQueryWrapper<OrderInfo>()
                .eq(OrderInfo::getOrderNo, orderNo)
                .eq(OrderInfo::getUserId, userId));
        if (orderInfo == null) {
            return new OrderInfo();
        }

        // 2 封装订单明细列表
        List<OrderDetail> orderDetailList = this.orderDetailMapper.selectList(new LambdaQueryWrapper<OrderDetail>()
                .eq(OrderDetail::getOrderId, orderInfo.getId()));
        orderInfo.setOrderDetailList(orderDetailList);
        // 3 封装订单减免列表
        List<OrderDerate> orderDerateList = this.orderDerateMapper.selectList(new LambdaQueryWrapper<OrderDerate>()
                .eq(OrderDerate::getOrderId, orderInfo.getId()));
        orderInfo.setOrderDerateList(orderDerateList);
        // 4 封装订单状态名称和支付方式名称
        String statusName = SystemConstant.ORDER_STATUS_MAP.getOrDefault(orderInfo.getOrderStatus(), "未知状态");
        orderInfo.setOrderStatusName(statusName);
        String payWayName = SystemConstant.ORDER_PAY_MAP.getOrDefault(orderInfo.getPayWay(), "未知方式");
        orderInfo.setPayWayName(payWayName);

        // 5 返回订单信息
        return orderInfo;
    }

    @Override
    public Page<OrderInfo> getUserOrderByPage(Page<OrderInfo> pageInfo, Long userId) {
        // 1 获取订单分页列表
        pageInfo = this.baseMapper.selectUserOrderByPage(pageInfo, userId);
        // 2 处理订单状态名称和支付方式名称
        pageInfo.getRecords().forEach(orderInfo -> {
            String statusName = SystemConstant.ORDER_STATUS_MAP.getOrDefault(orderInfo.getOrderStatus(), "未知状态");
            orderInfo.setOrderStatusName(statusName);
            String payWayName = SystemConstant.ORDER_PAY_MAP.getOrDefault(orderInfo.getPayWay(), "未知方式");
            orderInfo.setPayWayName(payWayName);
        });
        return pageInfo;
    }

    @Override
    public void cancelOrder(Long orderId) {
        // 查询订单信息
        OrderInfo orderInfo = this.baseMapper.selectById(orderId);
        // 判断订单状态，如果订单状态为未支付，则取消订单
        if (orderInfo != null && SystemConstant.ORDER_STATUS_UNPAID.equals(orderInfo.getOrderStatus())) {
            orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_CANCEL);
            this.baseMapper.updateById(orderInfo);
        }

    }

    @Override
    public void updateOrderOnPaySuccess(String orderNo) {
        // 1.根据订单编号查询订单信息。
        Long userId = AuthContextHolder.getUserId();
        OrderInfo orderInfo = this.getOrderInfoByOrderNo(userId, orderNo);
        // 2.判断订单信息，如果订单信息不存在或者订单状态为已支付，则返回。
        if (orderInfo == null || SystemConstant.ORDER_STATUS_PAID.equals(orderInfo.getOrderStatus())) {
            return;
        }
        // 3.更新订单状态为已支付
        orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_PAID);
        this.baseMapper.updateById(orderInfo);

        // 4. 远程调用用户服务保存用户购买记录
        UserPaidRecordVo userPaidRecordVo = new UserPaidRecordVo();
        userPaidRecordVo.setOrderNo(orderNo);
        userPaidRecordVo.setUserId(userId);
        userPaidRecordVo.setItemType(orderInfo.getItemType());
        List<Long> itemIdList = orderInfo.getOrderDetailList().stream().map(OrderDetail::getItemId).toList();
        userPaidRecordVo.setItemIdList(itemIdList);
        Result<Void> result = this.userFeignClient.savePaidRecord(userPaidRecordVo);
        if (Result.FAIL == result.getCode()) {
            throw new GuiguException(ResultCodeEnum.REMOTE_ERROR);
        }
    }
}
